docs(platform): design self-host stack supervision — keep pgid, decline Quadlet (RIG-3239) - #872
Merged
trunk-io[bot] merged 9 commits intoSep 6, 2026
Conversation
|
😎 Merged successfully - details. |
|
Compass engineering docs preview: https://compass-native-rig-3239-stac.compass-eng-docs.pages.dev Deployed from Changed pages: |
rigel-mintaka
force-pushed
the
compass-native/rig-3239-stack-supervision-design
branch
from
September 4, 2026 21:13
7dd3688 to
eb3ef67
Compare
rigel-mintaka
added a commit
that referenced
this pull request
Sep 4, 2026
…y (RIG-3239) Fold the single-review-of-record (ReviewStackSupervision872) on the stack-supervision design: 1 high, 5 medium, 4 low. All load-bearing Go citations were re-verified at the PR head before folding. High (lock-lifetime self-deadlock) — a genuine load-bearing fork on the Matt-ruled DL-183 interlock, not a coordinator ruling. `up --supervise` is the first caller to hold the state-dir lockfile with a live pid for the stack's whole lifetime, so `DownDetached`'s live-holder guard (`downdetached.go:74-84`) permanently refuses `down` and T1's pre-spawn sweep self-refuses. Folded the two factual defects it exposed (the false T2 stop-truth premise; the T1 item-4 ordering) and captured the mechanism as OQ-6 (release-at-Ready vs mode-token guard), recommendation (b), routed to Matt via RIG-3261 alongside OQ-5. Added a Global Constraints lock-lifetime invariant. Medium — OQ-4 `After=network.target` contradiction removed to match the ruled position; `main.go:30-31` miscite corrected to `main.go:42-45` (`defaultListenAddr`); Approach reason-#1 and OQ-5 darwin-port sites widened from one-seam to two-seam; T1 container-supervision fork ruled (liveness poll via `ContainerController.Exists`, unbounded-`podman wait` rejected) and the Interfaces `Health`/`Exists` roles split; `### Resolved decisions` promoted to `##` so the Open-Questions freeze scope holds only OQ-4/OQ-5/OQ-6. Low — OQ-5/OQ-6 routed to RIG-3261 in the headings; `golang.org/x/sys` promotion indirect→direct noted with the `go.mod`/`go.sum` delta added to T3 Produces; DL-328 row relocated from `## Infrastructure & CI` to `## Desktop shell` (placement only, row text byte-identical); garbled "non-systemd-agnostic" Alternatives prose fixed. Additive review-fix commit atop the PR head; no `--supervise` mechanism was ruled — OQ-6 is Matt's. Ledger-impact: DL-328 relocated within DECISIONS.md (no text/status change) Refs RIG-3239 Co-authored-by: Matt Wilkinson <matt@rigel.build>
rigel-mintaka
added a commit
that referenced
this pull request
Sep 4, 2026
…nsistency (RIG-3239)
ReviewStackSupervision872r2 confirmed all 10 prior findings closed and the
freeze gate PASS, but flagged three new mediums of the summary-vs-detail
drift class: the prior fold corrected the OQ-6 lock-lifetime premise at its
detail sites (T1 item 4, T2 stop-truth, Global Constraints) but left two
upstream Approach summaries and one T2->T1 cross-reference carrying the
pre-fold, now-falsified claims. Fold reconciles them:
- Approach :340 counted "the one remaining fork" (OQ-5) while Open Questions
now holds two load-bearing forks; names both OQ-5 and OQ-6.
- Approach :283-285 justified unit-aware `down` by a "undone by a restart"
rationale the T2 fold already retracted; rewritten to the live-holder-guard
model and flags the OQ-6 dependency.
- T2 stop-truth :648 delegated the OQ-6 behavior to "T1's smoke", which had no
such leg; added T1 test-cycle item (5) (`down` against a supervised stack
exits ZERO, never `ErrStackStarting`) and repointed the cross-ref.
- L1: T4 T3 tasks row still singular ("readStartTime"); widened to both
darwin seams (`readStartTime` + `readGroupLeaderStartTime`, one shared
encoding), matching the two-seam prose.
L2 (two ragged line-wraps, render-correct, no dropped text) deferred — not
churning a freeze-ready record for cosmetics.
Additive fold child off the bookmark tip; no DECISIONS.md change (DL-328
unchanged). Gates: markdownlint-cli2 0 errors (192 files); design-ledger-gate
OK (292 rows / 122 headers).
Refs RIG-3239
Co-authored-by: Matt Wilkinson <matt@rigel.build>
rigel-mintaka
added a commit
that referenced
this pull request
Sep 4, 2026
…ation + arity (RIG-3239) ReviewStackSupervision872r3 confirmed all 3 prior mediums + L1 genuinely closed at both detail and summary sites, but caught two live gating mediums of the recurring summary-vs-detail drift class in the freeze candidate: - M1 (new, from the r2 fold): T1 test-cycle item (5) stated an unconditional "exits ZERO", true only under OQ-6 option (b), contradicting the option-discriminating T2 stop-truth bullet and T1's own child-death non-zero exit contract. Rewrote item (5) to be option-agnostic — never wedges on ErrStackStarting and the stop lands with the ruling; under (b) the supervising holder is signalled and exits ZERO, under (a) the lock is released so down signals the children and the child-death path applies. Anchored it in-process to the OQ-6 counterpart of downdetached_test.go:238 (verified at source). Being option-agnostic, it no longer belongs in the OQ-6 (b)-dependent inventory (:854), closing M1's second site. - M2 (pre-existing, survived all three folds): the bolded "three interacting contract facts" summary counted a four-item list; item 4 (pre-spawn survivor cleanup) is the most cross-referenced fact (Global Constraint + OQ-6-(b) site), so the miscount is load-bearing. Fixed the count to four. - L1 (folded into M1's edit): item (5) now disambiguates the in-process harness assertion from the Linux process smoke. Gates: markdownlint-cli2 0 errors (192 files); design-ledger-gate OK (292 rows / 122 headers). Diff: design.md only, 11 insertions / 7 deletions. Ledger-impact: none (DL-328 cell unchanged) Refs RIG-3239 Co-authored-by: Matt Wilkinson <matt@rigel.build>
rigel-mintaka
added a commit
that referenced
this pull request
Sep 4, 2026
…ory + reason ordinal (RIG-3239) ReviewStackSupervision872r4 confirmed the r3 fold clean (M1 item-(5) option-discrimination, M2 arity 3->4, L1 harness-vs-process all genuinely closed at detail AND summary sites; no fold-introduced contradiction; freeze gate PASS; new downdetached_test.go:238 citation verified reachable at source), and caught two pre-existing instances of the summary-vs-detail drift class the fold never touched: - Medium (:858-859): the OQ-6 Recommendation's "if Matt rules (a), rewrite these" inventory named T1 item 4 and the T2 stop-truth bullet as (b)-dependent; neither is. T1 item 4's own parenthetical (:528-530) names (b) as the DECLINED alternative and calls the before-lock sweep "the option-independent fix", and Global Constraint :439-442 mandates the sweep "Whichever OQ-6 option lands" as a hard MUST — so the inventory instructed an executor who receives ruling (a) to fold away a requirement that survives (a) unchanged. The T2 bullet (:645-648) is option-discriminating and already carries both (a)/(b) branches. Second-order: the r3 fold made item (5) option-agnostic too, so the record now has ZERO genuinely (b)-dependent sites -> rewrote the sentence to state every T1/T2 site is option-agnostic and a ruling of (a) needs only deletion of moot (b) branches, not a rewrite (per the reviewer's suggested fix; NOT re-listed, which would re-falsify the M1 fix). - Low (:178-179): the first "keep pgid" reason called itself a "second, independent reason on top of the two below" (1 on top of 2 = third); the Resolved-decisions row for the same argument (:867) already says "third". Aligned to "third". Both folded coordinator-direct (judgment-dense frozen-record consistency). Gates: markdownlint-cli2 0 errors (192 files); design-ledger-gate OK (292 rows / 122 headers). Diff: design.md only, 9 ins / 3 del. Refs RIG-3239 Ledger-impact: none (DECISIONS.md unchanged) Co-authored-by: Matt Wilkinson <matt@rigel.build>
rigel-mintaka
marked this pull request as ready for review
September 5, 2026 23:46
…ne Quadlet (RIG-3239) Decides how the long-lived self-host stack services (compass-server, compass-runner, containerized postgres, OTel collector) are supervised. Ruling (Matt fork — OQ-1): KEEP the hand-rolled DL-183/DL-262 pgid mechanism as the single built-in supervision model; do NOT adopt per-service Podman Quadlet units. Quadlet is Linux/systemd-only, so the pgid path survives for the embedded + non-systemd tiers regardless — adopting Quadlet means two behaviorally-equivalent supervision models, the imperative cold sequence would need oneshot pre-units + sdnotify re-plumbing, and per-service units displace the DL-259-named `compass-stack up` verb. Optionally ship a documented thin systemd USER-unit wrapper (Type=oneshot + RemainAfterExit=yes) for boot-start/logout survival — systemd wrapping the supervisor, never replacing it. Docker-socket declined (daemon model vs the rootless/no-daemon invariant). The record was red-teamed by a design-critic pass before submission; its findings are folded: the "one model everywhere including macOS" claim corrected to "one portable model with a named unbuilt darwin start-time-seam port" (pgidfile.go's /proc reader is Linux-only, `up` refuses on darwin today); the crux argued on maintenance cost not slice size (DL-259 pins the whole self-host tier to Linux); the oneshot wrapper's latent failure modes (status-lie under RemainAfterExit, stop-what-you-didn't-start, PATH/linger/After) folded into OQ-4 + the T1 unit checklist; and the crash-recovery fork reshaped into OQ-3 as three options — accept the gap (v1), a blocking `compass-stack up --supervise` under Type=exec/Restart=on-failure (systemd whole-stack recovery with one model), or reopen Quadlet. Three load-bearing Matt forks ride to the design gate: OQ-1 (adopt Quadlet vs keep), OQ-2 (ship the T1 wrapper at all), OQ-3 (crash-recovery posture). Ledger: adds DL-328 (Infrastructure & CI section). Status stays Draft — freezes Active on merge. Refs RIG-3239 Co-authored-by: Matt Wilkinson <matt@rigel.build>
…ed-team (RIG-3239) Re-author the self-host stack supervision record against Matt's RIG-3258 ruling and fold the design-critic red-team on the result. The original draft weighed Quadlet-vs-pgid and proposed accepting the crash-recovery gap for v1; Matt ruled: ship a one-command constant-on service on ALL platforms with auto-start at reboot and REAL crash recovery. Ruling folded into the record: - Keep the DL-183/DL-262 pgid mechanism as the SINGLE cross-platform model; decline Quadlet on a third independent ground (Linux/systemd-only — structurally cannot meet the all-platforms bar). - Whole-stack crash recovery = a blocking `compass-stack up --supervise` foreground mode wrapped by the platform-native OS supervisor's restart policy (systemd `Type=exec` on Linux, launchd `KeepAlive` on macOS). - One-command `compass-stack service install`/`uninstall` writing + enabling the native unit; the OS supervisor supplies only restart/backoff/boot; the pgid mechanism stays the sole bring-up/teardown and status truth stays `compass-stack status`. Red-team corrections folded (core direction survived every attack): - T1: the supervise loop is NOT a free consumer of the stack core. Name the bounded core change it forces — `Process.Wait` is single-caller (a fan-in Wait plus drainChildren's Wait double-calls exec.Cmd.Wait), so the loop takes sole Wait ownership; container children's Wait is 120s-bounded (supervise polls their liveness instead of a restart-storm every ~2min); Wait's cancel path group-SIGKILLs (teardown on a non-cancelable ctx, the upLocked WithoutCancel pattern); a partial drain orphans a survivor across restart (pre-spawn record-consuming cleanup). - T2: pin `KillMode=mixed` / `AbandonProcessGroup=true` so the OS does not parallel-kill the children before the DL-183 ordered drain; pin `RestartSec`/`ThrottleInterval` + an explicit start-limit posture; drop the per-user-manager-ignored `After=network.target` (loopback bind); make the `down` verb unit-aware so it stops through the unit rather than being undone by a restart. - T3: the darwin identity reader is a TWO-site swap (spawn-side pgidfile seam AND down-side groupsignal reader) sharing one uint64 timeval encoding, with the existing mirror-test extended so they cannot drift. - OQ-5: fold the launchd crashloop cost into option (a) — an experimental label does not stop a KeepAlive crashloop, so (a) carries an install-time preflight + a bring-up-failure self-limit. Routed to Matt as RIG-3261. Ledger-impact: DL-328 rewritten to the ruled design (keep pgid, decline Quadlet, ship --supervise + service install), Status Active. Spec-impact: none Refs RIG-3239 Refs RIG-3261 Co-authored-by: Matt Wilkinson <matt@rigel.build>
…y (RIG-3239) Fold the single-review-of-record (ReviewStackSupervision872) on the stack-supervision design: 1 high, 5 medium, 4 low. All load-bearing Go citations were re-verified at the PR head before folding. High (lock-lifetime self-deadlock) — a genuine load-bearing fork on the Matt-ruled DL-183 interlock, not a coordinator ruling. `up --supervise` is the first caller to hold the state-dir lockfile with a live pid for the stack's whole lifetime, so `DownDetached`'s live-holder guard (`downdetached.go:74-84`) permanently refuses `down` and T1's pre-spawn sweep self-refuses. Folded the two factual defects it exposed (the false T2 stop-truth premise; the T1 item-4 ordering) and captured the mechanism as OQ-6 (release-at-Ready vs mode-token guard), recommendation (b), routed to Matt via RIG-3261 alongside OQ-5. Added a Global Constraints lock-lifetime invariant. Medium — OQ-4 `After=network.target` contradiction removed to match the ruled position; `main.go:30-31` miscite corrected to `main.go:42-45` (`defaultListenAddr`); Approach reason-#1 and OQ-5 darwin-port sites widened from one-seam to two-seam; T1 container-supervision fork ruled (liveness poll via `ContainerController.Exists`, unbounded-`podman wait` rejected) and the Interfaces `Health`/`Exists` roles split; `### Resolved decisions` promoted to `##` so the Open-Questions freeze scope holds only OQ-4/OQ-5/OQ-6. Low — OQ-5/OQ-6 routed to RIG-3261 in the headings; `golang.org/x/sys` promotion indirect→direct noted with the `go.mod`/`go.sum` delta added to T3 Produces; DL-328 row relocated from `## Infrastructure & CI` to `## Desktop shell` (placement only, row text byte-identical); garbled "non-systemd-agnostic" Alternatives prose fixed. Additive review-fix commit atop the PR head; no `--supervise` mechanism was ruled — OQ-6 is Matt's. Ledger-impact: DL-328 relocated within DECISIONS.md (no text/status change) Refs RIG-3239 Co-authored-by: Matt Wilkinson <matt@rigel.build>
…nsistency (RIG-3239)
ReviewStackSupervision872r2 confirmed all 10 prior findings closed and the
freeze gate PASS, but flagged three new mediums of the summary-vs-detail
drift class: the prior fold corrected the OQ-6 lock-lifetime premise at its
detail sites (T1 item 4, T2 stop-truth, Global Constraints) but left two
upstream Approach summaries and one T2->T1 cross-reference carrying the
pre-fold, now-falsified claims. Fold reconciles them:
- Approach :340 counted "the one remaining fork" (OQ-5) while Open Questions
now holds two load-bearing forks; names both OQ-5 and OQ-6.
- Approach :283-285 justified unit-aware `down` by a "undone by a restart"
rationale the T2 fold already retracted; rewritten to the live-holder-guard
model and flags the OQ-6 dependency.
- T2 stop-truth :648 delegated the OQ-6 behavior to "T1's smoke", which had no
such leg; added T1 test-cycle item (5) (`down` against a supervised stack
exits ZERO, never `ErrStackStarting`) and repointed the cross-ref.
- L1: T4 T3 tasks row still singular ("readStartTime"); widened to both
darwin seams (`readStartTime` + `readGroupLeaderStartTime`, one shared
encoding), matching the two-seam prose.
L2 (two ragged line-wraps, render-correct, no dropped text) deferred — not
churning a freeze-ready record for cosmetics.
Additive fold child off the bookmark tip; no DECISIONS.md change (DL-328
unchanged). Gates: markdownlint-cli2 0 errors (192 files); design-ledger-gate
OK (292 rows / 122 headers).
Refs RIG-3239
Co-authored-by: Matt Wilkinson <matt@rigel.build>
…ation + arity (RIG-3239) ReviewStackSupervision872r3 confirmed all 3 prior mediums + L1 genuinely closed at both detail and summary sites, but caught two live gating mediums of the recurring summary-vs-detail drift class in the freeze candidate: - M1 (new, from the r2 fold): T1 test-cycle item (5) stated an unconditional "exits ZERO", true only under OQ-6 option (b), contradicting the option-discriminating T2 stop-truth bullet and T1's own child-death non-zero exit contract. Rewrote item (5) to be option-agnostic — never wedges on ErrStackStarting and the stop lands with the ruling; under (b) the supervising holder is signalled and exits ZERO, under (a) the lock is released so down signals the children and the child-death path applies. Anchored it in-process to the OQ-6 counterpart of downdetached_test.go:238 (verified at source). Being option-agnostic, it no longer belongs in the OQ-6 (b)-dependent inventory (:854), closing M1's second site. - M2 (pre-existing, survived all three folds): the bolded "three interacting contract facts" summary counted a four-item list; item 4 (pre-spawn survivor cleanup) is the most cross-referenced fact (Global Constraint + OQ-6-(b) site), so the miscount is load-bearing. Fixed the count to four. - L1 (folded into M1's edit): item (5) now disambiguates the in-process harness assertion from the Linux process smoke. Gates: markdownlint-cli2 0 errors (192 files); design-ledger-gate OK (292 rows / 122 headers). Diff: design.md only, 11 insertions / 7 deletions. Ledger-impact: none (DL-328 cell unchanged) Refs RIG-3239 Co-authored-by: Matt Wilkinson <matt@rigel.build>
…ory + reason ordinal (RIG-3239) ReviewStackSupervision872r4 confirmed the r3 fold clean (M1 item-(5) option-discrimination, M2 arity 3->4, L1 harness-vs-process all genuinely closed at detail AND summary sites; no fold-introduced contradiction; freeze gate PASS; new downdetached_test.go:238 citation verified reachable at source), and caught two pre-existing instances of the summary-vs-detail drift class the fold never touched: - Medium (:858-859): the OQ-6 Recommendation's "if Matt rules (a), rewrite these" inventory named T1 item 4 and the T2 stop-truth bullet as (b)-dependent; neither is. T1 item 4's own parenthetical (:528-530) names (b) as the DECLINED alternative and calls the before-lock sweep "the option-independent fix", and Global Constraint :439-442 mandates the sweep "Whichever OQ-6 option lands" as a hard MUST — so the inventory instructed an executor who receives ruling (a) to fold away a requirement that survives (a) unchanged. The T2 bullet (:645-648) is option-discriminating and already carries both (a)/(b) branches. Second-order: the r3 fold made item (5) option-agnostic too, so the record now has ZERO genuinely (b)-dependent sites -> rewrote the sentence to state every T1/T2 site is option-agnostic and a ruling of (a) needs only deletion of moot (b) branches, not a rewrite (per the reviewer's suggested fix; NOT re-listed, which would re-falsify the M1 fix). - Low (:178-179): the first "keep pgid" reason called itself a "second, independent reason on top of the two below" (1 on top of 2 = third); the Resolved-decisions row for the same argument (:867) already says "third". Aligned to "third". Both folded coordinator-direct (judgment-dense frozen-record consistency). Gates: markdownlint-cli2 0 errors (192 files); design-ledger-gate OK (292 rows / 122 headers). Diff: design.md only, 9 ins / 3 del. Refs RIG-3239 Ledger-impact: none (DECISIONS.md unchanged) Co-authored-by: Matt Wilkinson <matt@rigel.build>
…G-3239) Matt ruled both remaining forks on RIG-3261/RIG-3239: - OQ-6 → (b): teach `DownDetached`'s live-holder guard a mode/state token — a supervise process parked at Ready is a valid `down` target (signalled, exits zero), a mid-bring-up `up` is still refused. Preserves DL-183 single-owner mutual exclusion that option (a) would have traded away. - OQ-5 → ship macOS supervision now, runtime-agnostic: the launchd LaunchAgent supervises the `compass-stack` host process independent of the runtime inside it (podman today, apple-container later), so macOS supervision is NOT gated on the RIG-3238 backend choice. The OQ-5-as-filed podman-machine-vs-RIG-3238 fork dissolves; the residual macOS GA gate is the stack-topology validation owned by the sibling lane, not a supervision fork. Both folded into Resolved decisions; the (a)/(b) fork prose collapses to the ruled (b) across Approach / Global Constraints / T1 / T2 / T3; Open Questions now holds only OQ-4 (non-load-bearing content checklist). Status: Draft → Active. Ledger-impact: none (DL-328 already captures the supervision decision; the OQ-6 guard-token is the internal mechanism DL-328's "DL-183 spawn/teardown unchanged" already anticipates). Spec-impact: none. Refs RIG-3239 Co-authored-by: Matt Wilkinson <matt@rigel.build>
…-3239) sync-before-submit fix: rebased onto current main, whose ledger tail advanced to DL-328 (gateway_credentials encryption) since this branch was cut. The record's own ID-allocation guard mandates taking the next free id when the claimed number is taken — DL-329. Renumbered the DECISIONS.md row + the record's ID-allocation prose + the two T4 self-references; folded the runtime-agnostic macOS + OQ-6 mode-token clauses into the DL-329 row. gateway-creds DL-328 untouched. Ledger-impact: renumber only (DL-328 -> DL-329); no decision content change. Spec-impact: none. Refs RIG-3239 Co-authored-by: Matt Wilkinson <matt@rigel.build>
rigel-mintaka
force-pushed
the
compass-native/rig-3239-stack-supervision-design
branch
from
September 5, 2026 23:57
8be4d4c to
38de521
Compare
rigel-mintaka
added a commit
that referenced
this pull request
Sep 6, 2026
…ulings (RIG-3238)
Matt ruled all five Matt-fork open questions on RIG-3246 (2026-09-05), reorienting the record from "opt-in behind podman-machine, two permanent macOS backends" to "apple-container is THE macOS embedded engine":
- OQ-9 -> A: commit the mac mini on Woodpecker (ssh access provisioned); it runs the T-1 spike + T-3 live suite. GHA keeps the Linux lanes.
- OQ-10 -> sunset podman-machine on macOS; Intel + macOS <= 15 out of scope ("Apple has basically ended all support for Intel Macs"). No permanent two-backend matrix.
- OQ-11 -> vsock, mirroring the microVM's guestd forwarder (`go/internal/guestd/gateway_proxy.go:29-33`). Settles the darwin socket transport once.
- OQ-12 -> runner stays HOST-SIDE (darwin-native) driving apple-container over vsock, NOT runner-in-VM ("that's what we do for microVMs anyway too"). Overrides the older compass-local-dev in-VM ruling; agrees with embedded-revival OQ-7.
- OQ-13 -> all on apple-container, no podman: postgres + collector move onto apple-container too (T-2 ports the podman-hardwired stack shell + `ImageEnsurer`).
Folded across Approach (new ruling + spike-now/build-later sequencing), Global Constraints (interim-not-permanent podman-machine, sunset, DL-330 id), the vsock transport into the backend-plugs-in + egress sections, T-2 (postgres moves) + T-4 (runner host-side, not conditional). OQ-9..OQ-13 moved to a new Resolved decisions section; OQ-2 reframed from open transport fork to spike-confirms-the-vsock-leg. Open Questions now holds only OQ-1/OQ-3/OQ-4/OQ-5 (spike-resolvable T-1 line items) + OQ-6/OQ-7/OQ-8 (non-load-bearing). Status: Draft -> Active.
Ledger: adds DL-330 (apple-container is the macOS engine). Rebased onto current main whose ledger tail advanced to DL-328; the record's originally-claimed DL-326 was taken (session-volume clone), and the in-flight #872 claims DL-329, so this took DL-330 per the record's ID-allocation guard. The prior 5-commit review-fold stack was squashed into the base design commit during the rebase conflict-resolution (the DL-326 collision), so this reorientation lands as one fold commit atop it.
Ledger-impact: adds DL-330.
Spec-impact: none. Refs RIG-3238
Co-authored-by: Matt Wilkinson <matt@rigel.build>
rigel-mintaka
force-pushed
the
compass-native/rig-3239-stack-supervision-design
branch
2 times, most recently
from
September 6, 2026 02:43
5705370 to
8af7f77
Compare
Review of the freeze fold (0H/0M gate) returned 2M/2L — the recurring summary-vs-detail drift class, this time inverted: the fold ADDED a new load-bearing deliverable to the OQ-5 Resolved summary with no detail site to back it. Closed: - M1 (crashloop guard): the fold folded the red-team's darwin install-time preflight + never-Ready self-limit into the OQ-5 Resolved bullet as shipped-now, but it existed at NO detail site and contradicted the Division-of-labor paragraph (which said the OS start-limit IS the ceiling). Landed the guard at every detail site: T1 Produces + test cycle (the never-Ready BRING-UP self-limit, distinct from the attached-probe N-consecutive degradation); T2 Produces (service install runs the install-time preflight and refuses on failure) + unit-content gate + test cycle (preflight-refusal unit test); T1/T2 Tasks bullets. Reconciled the Division-of-labor paragraph: the OS start-limit is the ceiling for POST-Ready crash restarts, the crashloop guard bounds the never-Ready path (throttle alone would KeepAlive-loop a misconfigured host). - M2 (provenance): the OQ-5 ruling was tagged "(Matt, RIG-3239, 2026-09-05)" but RIG-3239 has zero comments — the ruling is a genuine 2026-09-05 session directive. Retagged both sites (OQ-5 Resolved bullet + the Approach prose) to "session directive". Mirrored the directive as a RIG-3239 comment so the cite is durably resolvable. - L1 (stale cross-ref): the OQ-12 forward-reference cited apple-container-macos-runner/design.md:713-733 + a "cannot run natively on darwin" quote that no longer exists post-#869-reorientation (OQ-12 was ruled the opposite way — runner IS host-side darwin-native). Dropped the stale line-range + quote for a range-free reference reflecting the ruled state; fixed the Problem/Intent echo too. Verified: markdownlint 0, design-ledger-gate OK (296 rows), provenance-leftover + stale-cite scans clean, guard referenced at 17 sites. Re-review confirmed M2 + L1 closed but M1 only PARTIALLY — the crashloop guard was physically present at all detail sites, but the never-Ready self-limit MECHANISM was unimplementable as specified. Three new mediums + three lows, now closed: - M-1 (self-limit reachability + counter): the self-limit lived in `Supervise`, but a never-Ready `Up` returns nil `*Stack` and never enters it; and N-consecutive failures span OS-supervisor RESTARTS (separate processes), so an in-process counter resets each loop. Relocated the self-limit to the CLI supervise wrapper (`main.go`) that owns the `Up` retry, with the failure counter PERSISTED in the state dir (reset on Ready); corrected T1 test item (6) to name the CLI-level loop (the internal/stack harness can't exercise it). - M-2 (terminal-exit knob): no unit-template knob expressed the "terminal non-restart exit" — systemd `Restart=on-failure` / launchd `KeepAlive={SuccessfulExit=false}` restart on ANY non-zero. Pinned the reserved code (78/EX_CONFIG): systemd `RestartPreventExitStatus=78`; launchd has no per-code exemption so the wrapper self-`bootout`s its agent on the terminal path. Added both to the templates, the unit-content gate, and the T2 test cycle; corrected the OQ-4 "both rendered/asserted by T2" claim to name half 2's correct owners (behavior T1, exit-knob T2). - M-3 (cross-platform preflight): the install-preflight was macOS-only ("podman machine reachable") for a cross-platform verb; on Linux podman is native. Restated per-platform (darwin: podman machine; linux: rootless podman + real XDG_RUNTIME_DIR) + one probe cycle on both; bound it to REUSE the existing `preflight` verb rather than a divergent check; made the T2 refusal test cover the linux path too (the all-platforms bar forbids a macOS-only close). - L-1: qualified the Approach "crashloop ceiling" phrasing to POST-Ready. L-2: restored a range-free forward-ref caveat (the #869 record isn't merged yet). L-3: aligned the DL-329 ledger provenance tag with the record (session directive, mirrored on RIG-3239). Scoped re-review of the M-1/M-2/M-3 interdiff returned FREEZABLE (0H/0M/3L). Closed two of the three non-gating lows (the third, a cosmetic soft-wrap, is left as-is per the reviewer's explicit non-gating note): - L-1 (preflight-reuse citation scope): the M-3 fix bound half 1 to "REUSING the existing `preflight` verb", but `runPreflight` (`cmd/compass-stack/preflight.go:86-110`) runs `checkKVM()` + the microVM-trio floors (`hostcheck.go:42-46`) that gate the microVM RUNNER, not the supervised stack — and darwin has no `/dev/kvm`, so as-cited it would refuse install on every Mac. Softened to reuse the verb's per-check machinery with a supervision-scoped check set (`internal/preflight/preflight.go:28-34` `MachineReady`, darwin-only/linux-absent), explicitly excluding the KVM/microVM floors; swept the OQ-4 summary site to match. - L-3 (launchd never-Ready coverage): T1 test item (6) asserted only the systemd-shaped terminal exit. Extended it per-platform — linux exits the reserved code (78/EX_CONFIG), darwin additionally invokes the self-`bootout`/`disable` seam (stubbed at the CLI boundary, asserted called once with the agent label), since launchd keys on zero-vs-non-zero with no per-code exemption. Verified: new cites resolve at head (`MachineReady`, `runPreflight`, `MicroVMFloors`); markdownlint 0; design-ledger-gate OK (296 rows). A second scoped re-review of that interdiff confirmed both closures (no new summary/detail split: all 12 preflight mentions enumerated, the two that assert the reuse mechanism both swept; the launchd mechanism agrees across all four sites) and returned FREEZABLE 0H/0M/3L. Those three lows are now dispositioned as fixes rather than deferrals: - The darwin `MachineReady` seam has no adapter wired at head and its absence is SILENT, not failing (`internal/preflight/preflight.go:117` guards `GOOS == "darwin" && MachineReady != nil`; the sole wiring site `compass-app/embedded.go:379-383` omits it, pending embedded-revival T-6). Following the citation as written would have produced an install-time preflight that silently passes on a mac with a dead podman machine — inert on exactly the platform this record just cleared to ship. Added the obligation to the T2 Produces darwin item: T2 either wires a darwin machine probe itself or the darwin half of guard 1 is inert until T-6. - The unit-content gate listed "launchd self-`bootout`" alongside the systemd knob as if both were renderable unit content; they are not symmetric (the launchd path is wrapper runtime behavior with nothing to render). Marked it cross-referenced rather than rendered, pointing at OQ-4's ownership split. - Reflowed the soft-wrap stub tails the two fixes left in the T1 item (6), T2 Produces, and OQ-4 paragraphs. Verified: the new `preflight.go:117` + `embedded.go:379-383` cites resolve at head; markdownlint 0; design-ledger-gate OK (296 rows). Ledger-impact: none. Spec-impact: none. Refs RIG-3239 Co-authored-by: Matt Wilkinson <matt@rigel.build>
rigel-mintaka
force-pushed
the
compass-native/rig-3239-stack-supervision-design
branch
from
September 6, 2026 03:16
8af7f77 to
b62f021
Compare
mattwilkinsonn
approved these changes
Sep 6, 2026
trunk-io Bot
pushed a commit
that referenced
this pull request
Sep 6, 2026
…ulings (RIG-3238) (#869) * docs(platform): freeze apple-container macOS runner — fold RIG-3246 rulings (RIG-3238) Matt ruled all five Matt-fork open questions on RIG-3246 (2026-09-05), reorienting the record from "opt-in behind podman-machine, two permanent macOS backends" to "apple-container is THE macOS embedded engine": - OQ-9 -> A: commit the mac mini on Woodpecker (ssh access provisioned); it runs the T-1 spike + T-3 live suite. GHA keeps the Linux lanes. - OQ-10 -> sunset podman-machine on macOS; Intel + macOS <= 15 out of scope ("Apple has basically ended all support for Intel Macs"). No permanent two-backend matrix. - OQ-11 -> vsock, mirroring the microVM's guestd forwarder (`go/internal/guestd/gateway_proxy.go:29-33`). Settles the darwin socket transport once. - OQ-12 -> runner stays HOST-SIDE (darwin-native) driving apple-container over vsock, NOT runner-in-VM ("that's what we do for microVMs anyway too"). Overrides the older compass-local-dev in-VM ruling; agrees with embedded-revival OQ-7. - OQ-13 -> all on apple-container, no podman: postgres + collector move onto apple-container too (T-2 ports the podman-hardwired stack shell + `ImageEnsurer`). Folded across Approach (new ruling + spike-now/build-later sequencing), Global Constraints (interim-not-permanent podman-machine, sunset, DL-330 id), the vsock transport into the backend-plugs-in + egress sections, T-2 (postgres moves) + T-4 (runner host-side, not conditional). OQ-9..OQ-13 moved to a new Resolved decisions section; OQ-2 reframed from open transport fork to spike-confirms-the-vsock-leg. Open Questions now holds only OQ-1/OQ-3/OQ-4/OQ-5 (spike-resolvable T-1 line items) + OQ-6/OQ-7/OQ-8 (non-load-bearing). Status: Draft -> Active. Ledger: adds DL-330 (apple-container is the macOS engine). Rebased onto current main whose ledger tail advanced to DL-328; the record's originally-claimed DL-326 was taken (session-volume clone), and the in-flight #872 claims DL-329, so this took DL-330 per the record's ID-allocation guard. The prior 5-commit review-fold stack was squashed into the base design commit during the rebase conflict-resolution (the DL-326 collision), so this reorientation lands as one fold commit atop it. Ledger-impact: adds DL-330. Spec-impact: none. Refs RIG-3238 Co-authored-by: Matt Wilkinson <matt@rigel.build> * docs(platform): close review findings on the RIG-3246 fold (RIG-3238) Review of the reorientation fold (0H/0M gate) returned 2H/7M/2L — the recurring summary-vs-detail drift class recurred once more (the fold rewrote the Approach/Global-Constraints/OQ-2/Resolved-decisions summary sites but left the Plan bullets, Tasks checklist, Alternatives, T-5 matrix and Problem/Intent still asserting the pre-fold framing) plus a ledger structural defect. All closed: - H1 (ledger): the DL-330 row was orphaned — the DL-325 blockquote note + blank line terminated the markdown table, so DL-330 rendered as a paragraph, not a citable row (design-ledger-gate is blind to it; GitHub's renderer confirmed). Re-emitted the `| ID | Decision | Status | Record |` header pair before DL-330 (the First-turn-delivery precedent). Verified via the GitHub markdown API: DL-330 now renders as `<td>DL-330</td>`. - H2 + M6 (spike legs): rewrote T-1(b) from the raw-AF_UNIX-bind-mount experiment to the ruled vsock experiment (VZVirtioSocketDevice reachable through the container CLI, guestd-style forwarder, connect+round-trip over vsock); re-enumerated the Tasks checklist to all six legs with the vsock transport. - M1 (T-1c): dropped the two-way "whatever transport gets selected" framing; the vsock hop is out-of-band of the guest netfilter so no gateway carve-out, re-confirming the OQ-2/OQ-3 coupling is dissolved. - M2 (Alternatives): the OQ-7 socket-transport hazard is dissolved by the ruled vsock transport, not a virtiofs coin-flip. - M3 (T-5): the flip begins podman-machine's macOS sunset; Intel/macOS <= 15 are out of scope, not a permanent second supported arm. - M4/M5 (Problem/Intent + self-description): past-tensed the central fork (ruled in Approach); the record carries Matt's ruling + an adoption plan whose BUILD is spike-gated, not a recommendation. - M7 (OQ-5): re-tagged [non-load-bearing] and dropped the Matt-confirm request — the one-version-floor dependency is intrinsic to the committed RIG-3246 direction, mitigated by the version-floor probe + pinned-floor policy. - L1/L2: stray `**`; aligned T-1(f) register with T-4. Verified: markdownlint 0, design-ledger-gate OK (296 rows), residual-drift scan clean, DL-330 renders as a real table cell. Re-review confirmed all 11 prior findings closed + DL-330 a real table row, but caught 2 new mediums of the same drift class (summary/detail split) + 1 low, now also closed: - N1 (OQ-3 body): OQ-3 still stated "the ruleset must not sever the guest↔host gateway hop the agent socket rides" — the pre-vsock IP-hop premise the ruled transport dissolves. Dropped the agent-socket leg (it rides vsock, out-of-band of netfilter, no carve-out); kept the DNS + nftables-support + arming-order legs (genuinely still unverified). - N2 (OQ-5): the version-floor probe was cited as (T-1(e)), but that leg is timings/stability — the probe is T-2's `VerifyAppleContainerSupport`. Corrected to (T-2). - L-N2: re-flowed the ragged wrap in the Alternatives paragraph. (L-N1, the two-tables-under-one-heading shape, is the only structurally correct H1 fix — left as-is per the reviewer.) Ledger-impact: none (DL-330 unchanged in substance; the header-pair fix makes it render as a row). Spec-impact: none. Refs RIG-3238 Co-authored-by: Matt Wilkinson <matt@rigel.build> --------- Co-authored-by: Matt Wilkinson <matt@rigel.build>
trunk-io
Bot
deleted the
compass-native/rig-3239-stack-supervision-design
branch
September 6, 2026 03:59
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Decides how the long-lived self-host stack services (compass-server,
compass-runner, the containerized postgres, the bundled OTel collector) run
as a constant-on, auto-restarting service on all platforms — Linux and
macOS — with one-command install, auto-start at reboot, and whole-stack crash
recovery.
Ruling (Matt, RIG-3258, 2026-09-04)
This record was re-authored against Matt's ruling; the OQ-1/OQ-2/OQ-3 forks
that first shipped as open are now decided:
pgid spawn + identity-token teardown as the SINGLE cross-platform
supervision model. Podman Quadlet is Linux/systemd-only and structurally
cannot express the macOS half, so it fails the all-platforms bar; adopting
it would mean two behaviourally-equivalent models, oneshot pre-unit +
sdnotify re-plumbing of the imperative cold sequence, and per-service units
displacing the DL-259-named
compass-stack upverb.Linux-only doc snippet but
compass-stack service install/uninstallonall platforms, auto-start at reboot. Verbatim: "we need to ship with a one
command way to stand up the stack as a constant-on service on all
platforms".
posture is rejected ("a VPS doesn't fix a crash … opt 1 reasoning makes no
sense"); recovery ships as a blocking
compass-stack up --superviseforeground mode under the OS supervisor's restart policy, keeping one
supervision model.
Docker-socket is declined at the stack layer (daemon model vs the
rootless/no-daemon invariant, no per-container keep-id equivalent), mirroring
its per-session-runner rejection.
Design
Two additions, one supervision model. The OS supervisor (systemd user unit on
Linux, launchd LaunchAgent on macOS) supplies ONLY restart/backoff/boot-start;
DL-183 spawn order and identity-token teardown remain the sole
bring-up/teardown mechanism, and
compass-stack statusstays the single truthfor stack health.
compass-stack up --supervise: foreground blocking mode. Runs theexisting
Upto Ready, then blocks watching the children; a child deathdrains the stack and exits non-zero so the OS supervisor's restart policy
fires.
compass-stack service install/uninstall+ unit templates:platform-detect (
runtime.GOOS), render the platform-native unit from anembedded template with the resolved absolute binary path, install + enable
it (systemd
Type=exec/Restart=on-failure/TimeoutStopSec=90;launchd
RunAtLoad/KeepAlive={SuccessfulExit=false}/ExitTimeOut=90).darwin implementation so
upno longer refuses on macOS (two swaps in twopackages).
section (both platforms + the
compass-stack statusstatus-truth caveat),the DL-328 ledger row, RIG-3239 close-out.
Design process
Design-critic red-teamed before submission (9 folds, core direction sound);
review-fold loop ran r1 (1H/5M/4L) → r2 (0H/3M/2L) → r3 (0H/2M/2L) →
r4 (0H/1M/1L) → r5 (0H/0M/1L, clean at the gate). Every finding folded with
source-verified grounding at the PR head.
Open forks — tracked RIG-3261 (Matt)
Two load-bearing forks ride to the design gate; the record cannot freeze
(merge) until Matt rules them:
podman-machine now, or gate macOS GA on the RIG-3238 Apple-container
backend?
--superviselock lifetime:up --superviseis the first callerto hold the state-dir lockfile with a live pid for the stack's whole
lifetime; release the lock at Ready, or teach the down-guard a supervising
holder?
Ledger
Adds DL-328. Status stays Draft — the record freezes Active on merge,
in the same commit that folds the RIG-3261 ruling.
Refs RIG-3239
Co-authored-by: Matt Wilkinson matt@rigel.build